package com.lognsoft.myssh2;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
import java.util.ArrayList;
import java.io.File;
import java.io.FilenameFilter;
import java.util.HashMap;

import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.SCPClient;

public class myssh2
{
	private Connection conn;
	private Session    sess;
	private String     RemoteHomePath;
	private String     RemoteCurrentPath;
	private String     RemoteFullCurrentPath;
	private int TimeoutValue;
	private HashMap<String,String> RemotePaths = new HashMap<String,String>(); 
	
	/*  format server=www.server.com:home=/home/guanlei:user=coco:pwd=password 
	 *  format server=www.server.com:home=guanlei:user=coco:key-file=isa_file
	 * */	
	private static Properties parser_conn_string (String conn)
	{
		Properties ret = new Properties();
		String[] lst;
		
		if (conn == null)
		{
			return ret;
		}
		
		lst = conn.split(":");
		
		if (lst == null)
		{
			return ret;
		}		
		
		for (String s : lst)
		{
			int ind = s.indexOf('=');
			
			if (ind <=0)
			{
				continue;
			}
			
			ret.setProperty(
					s.substring(0,ind),
					s.substring(ind+1)
			);
		}
		
		return ret;
	};
	
	private static void usage ()
	{
		System.out.println ( "myssh2 0.0.1, Created by Coco Guan(guan52@gmail.com)");
		System.out.println ( "  This is a ssh tool for remote execution and file transfor");
		System.out.println ( "");
		System.out.println ( "usage:");
		System.out.println ( "  myssh2 [options] command arguments");
		System.out.println ( "");		
		System.out.println ( "command:");
		System.out.println ( "    run command line");
		System.out.println ( "    setup");
		System.out.println ( "    put local-file [remote-file] [mode]");
		System.out.println ( "    put local-path [remote-path]");
		System.out.println ( "    put file-filter [remote-path]");
		System.out.println ( "    get");
		System.out.println ( "");												
		System.out.println ( "options:");		
		System.out.println ( "    --timeout=timeout value in second");
		System.out.println ( "    --i=prvate key file");
		System.out.println ( "    --p=remote current path");
		System.out.println ( "    --home=remote host path");
		System.out.println ( "    --server=server address");
		System.out.println ( "    --user=login user");
		System.out.println ( "    --password=password for login user");
		System.out.println ( "               it will be ignored when -i set");
		System.out.println ( "");				
		System.out.println ( "connection-string : Connection is a default options for myssh2");
		System.out.println ( "    environment name:MYSSH2_CONN_STRING");
		System.out.println ( "    format:");
		System.out.println ( "      server=ip:user=name:password=:key-file=xx:home=path");
		System.out.println ( "    server  : same as --server=");
		System.out.println ( "    home    : same as --home=");
		System.out.println ( "    server  : same as --user=");
		System.out.println ( "    password: same as --password=");
		System.out.println ( "    key-file: same as --i=");		
		System.exit(0);
	}
	
	private void cmd_setup (String[] args) throws Exception
	{
		ArrayList<String> lines = new ArrayList<String> ();
			
		try
		{
			String fname;
			if (args != null && args.length > 0 )
			{
				fname = args[0];
			}
			else
			{
				fname = "myssh2.list";
			}
			
			BufferedReader rd = new BufferedReader (
					new FileReader (fname) );
			
			String s;

			while ( (s = rd.readLine()) != null )
			{
				lines.add(s.trim());
			}
		}
		catch (IOException io) 
		{
		}
		
		if (RemoteHomePath.length() > 0 )
		{
			sess.execCommand( "mkdir -p " + RemoteHomePath );
			
			printResult ();
		}
		
		for (String ll:lines)
		{			
			cmd_put ( new String[]{ll} );
		}
	}
	
	private void cmd_run (String[] args) throws Exception
	{
		if (args == null || args.length == 0)
		{
			System.out.println (" no command ");
			return;
		}
		
		String s = args[0];
		
		for (int i = 1; i < args.length; i++)
		{
			s += " ";
			s += args[i];
		}
		
		if (RemoteFullCurrentPath.length() > 0 )
		{
			s = "cd " + RemoteFullCurrentPath + " && " + s;
		}
		
		sess.execCommand( s );
		
		printResult ();
	}
	
	private void cmd_shell (String[] args)  throws Exception
	{
		
	}
	
	/*
	 * --------------------put_file
	 * put filename
	 * put filename remote-path/filename
	 * put filename remote-path/filename mode
	 * 
	 * --------------------put_path
	 * put path
	 * put path  remote-path
	 * 
	 * --------------------put_filter
	 * put *?
	 * put *?    remote-path
	 * 
	 * 
	 * */
	private void cmd_put (String[] args ) throws Exception
	{		
		if (args == null || args.length == 0)
		{
			usage();
		}
		
		for (int i = 0 ;i<args.length; i++)
		{
			args[i] = args[i].replace('\\', '/');
		}		
 
		if ( args[0].indexOf('*') >=0 || args[0].indexOf('?') >=0 )
		{
			put_filter (args);
			return;
		}
		
		File localPath = new File(args[0].replace('/',File.separatorChar));
		
		if (localPath.exists() == false)
		{
			System.err.println( args[0].replace('/',File.separatorChar) + " not found");
			return;
		}
		
		if (localPath.isDirectory())
		{
			put_path (args);
		}
		else
		{
			put_file (args);
		}
	}
	
	private void put_path  (String[] args ) throws Exception
	{
		String filePath = args[0];		
		String remotePath = args.length == 1 ? filePath : args[1];
		
		if (filePath.endsWith("/") == false)
		{
			filePath += "/";
		}
		
		if (remotePath.endsWith("/") == false)
		{
			remotePath += "/";
		}
		
		File localFile = new File (filePath.replace('/',File.separatorChar));
		
		if (localFile.exists() == false)
		{
			System.err.println( filePath.replace('/',File.separatorChar) + " not found");
			return;
		}
		
		File[] files = localFile.listFiles();
		
		if (files == null)
		{
			return;
		}		

		auto_mkdir ( remotePath );
		
		for (File ff : files)
		{
			if (ff.isDirectory())
			{
				continue;
			}
				
			put_file ( new String[] {
					filePath + ff.getName(),
					remotePath + ff.getName()
			});
		}
		
		for (File ff : files)
		{
			if (ff.isDirectory() == false)
			{
				continue;
			}
			
			if (ff.getName().equals(".") || ff.getName().equals(".."))
			{
				continue;
			}
			
			put_path ( new String[] {
					filePath + ff.getName(),
					remotePath + ff.getName()
			});
		}		
	}

	private void put_filter   (String[] args ) throws Exception
	{
		String fileName;
		String filePath;
		String remotePath;
		File localFile;
		
		fileName = args[0];
		
		int ind = fileName.lastIndexOf('/');

		if (ind >= 0)
		{
			filePath = fileName.substring(0, ind + 1);
			localFile = new File( filePath.replace('/',File.separatorChar) );				
			fileName = fileName.substring(ind + 1);				
		}
		else
		{
			localFile = new File(".");
			filePath = "";
		}
		
		if (localFile.exists() == false)
		{
			System.err.println( filePath.replace('/',File.separatorChar) + " not found");
			return;
		}
		
		FilenameFilter filter = new FilenameFilter()
		{
			@Override
			public boolean accept(File dir, String name)
			{
				return dir.isDirectory() == false;
			}
		};
		
		String[] fileNames = localFile.list(filter);

		if (fileNames == null)
		{			
			return;
		}
		
		remotePath = filePath;
		
		if (args.length >= 2)
		{
			remotePath = args[1];
		}
		
		if (remotePath.length() >= 1 && remotePath.endsWith("/") == false)
		{
			remotePath += "/";
		}
		

		System.out.println("begin copy ");
		
		for (String s : fileNames)
		{			
			System.out.println("filePath:" + filePath);
			System.out.println("remotePath:" + remotePath);
			System.out.println("filename:" + s);

			put_file ( new String[] {
					filePath + s,
					remotePath + s
			});
		}		
	}
	
	private void auto_mkdir(String remotePath) throws Exception
	{
		if (remotePath == null || remotePath.isEmpty())
		{
			return;
		}
		
		String createResult = RemotePaths.get(remotePath);
		
		if (createResult == null)
		{
			Session ss = conn.openSession();
			ss.execCommand("mkdir -p " + remotePath);
			ss.close();
			RemotePaths.put(remotePath, "OK");
		}
		else
		{
			
		}		
	}
	private void put_file (String[] args ) throws Exception
	{		
		String remoteFileName;
		String remotePath;
		String remoteMode;
		
		if (args.length == 1)
		{
			remoteFileName = args[0];
			
			while (remoteFileName.startsWith("/"))
			{
				remoteFileName = remoteFileName.substring(1);
			}
			
			remoteMode = "0600";
		}
		else if (args.length == 2)
		{
			remoteFileName = args[1];
			remoteMode = "0600";
		}
		else 
		{
			remoteFileName = args[1];
			remoteMode = args[2];
		}
		
		if (remoteFileName.startsWith("/") == false)
		{
			remoteFileName = RemoteFullCurrentPath + remoteFileName;
		}
		
		int ind = remoteFileName.lastIndexOf ('/');
		
		if (ind > 0)
		{
			remotePath = remoteFileName.substring(0,ind);
			remoteFileName = remoteFileName.substring(ind + 1);
		}
		else
		{
			remotePath = "";
		}
		
		auto_mkdir (remotePath);
		
		SCPClient scp = new SCPClient (conn);		 
		System.out.println("Put " + args[0] + " --> " + remoteFileName + "(at " + remotePath + ")");
		scp.put (args[0], remoteFileName, remotePath, remoteMode);
	}	
	
	private void cmd_get (String[] args) throws Exception
	{
		if (args == null || args.length == 0)
		{
			usage();
		}		
	}

	private void cmd_setenv (String[] args) throws Exception
	{
		
	}
	
	private void cmd_getenv (String[] args) throws Exception
	{
		
	}
	
	private void cmd_env (String[] args) throws Exception
	{
		
	}
	
	private void printResult () throws Exception
	{
		InputStream stdout = sess.getStdout();
		InputStream stderr = sess.getStderr();
		OutputStream stdin = sess.getStdin();
		
		byte[] buffer = new byte[8192];
		int len;
		int timeout_count = 0;
		
		while (true)
		{
			if (stdout.available() > 0)
			{
				len = stdout.read(buffer);
				if (len>0)
				{
					System.out.write(buffer, 0 , len);
				}
				continue;
			}
			
			if (stderr.available() > 0)
			{
				len = stderr.read(buffer);
				if (len>0)
				{
					System.err.write(buffer, 0 , len);
				}
				continue;
			}
			
			if ( System.in.available() > 0 )
			{
				len = System.in.read(buffer);
				if (len > 0)
				{
					stdin.write (buffer, 0, len);
				}
				continue;
			}
			
			/* Even though currently there is no data available, it may be that new data arrives
			 * and the session's underlying channel is closed before we call waitForCondition().
			 * This means that EOF and STDOUT_DATA (or STDERR_DATA, or both) may
			 * be set together.
			 */

			int conditions = sess.waitForCondition(
					ChannelCondition.STDOUT_DATA | 
					ChannelCondition.STDERR_DATA | 
					ChannelCondition.EOF, 100);		
			
			
			/* Wait no longer than 2 seconds (= 2000 milliseconds) */

			if ((conditions & ChannelCondition.TIMEOUT) != 0)
			{
				timeout_count++;
				
				if (timeout_count > TimeoutValue )/* A timeout occured. */
				{
					throw new IOException("Timeout while waiting for data from peer.");
				}
				else
				{
					continue;
				}
			}
			timeout_count = 0;

			/* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */

			boolean quit_flag = 
				(conditions & ChannelCondition.EOF) != 0 && 
				(conditions & ChannelCondition.STDERR_DATA) == 0 &&
				(conditions & ChannelCondition.STDOUT_DATA) == 0;
			
			if (quit_flag)
			{
				break;
			}
		}
	};
	
	public static void main (String[] args)
	{
		myssh2 ssh2 =  new myssh2();
		
		try
		{
			ssh2.broker(args);
		}
		catch (IOException e)
		{
			e.printStackTrace(System.err);
			System.exit(2);
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.exit(3);
		}
	}
	
	private void broker(String[] args) throws Exception
	{
		Properties commandArgs = new Properties();
		Properties envArgs = parser_conn_string ( System.getenv("MYSSH2_CONN_STRING") );
		String command = "";	
		String[] newArgs = null;
		
		/* run , shell, get, put, setenv , getenv, env */ 		

		for (int i = 0; i < args.length; i++)
		{
			String p = args[i];
			
			if (p.startsWith("--"))
			{
				p = p.substring(2);
				int ind = p.indexOf('=');
				
				if (ind > 0 )
				{
					commandArgs.setProperty(p.substring(0,ind), p.substring(ind + 1));
				}
				else
				{
					commandArgs.setProperty(p,"");
				}
			}
			else
			{
				command = p.toLowerCase();
				i++;
				
				if ( i >= args.length )
				{
					break;
				}
				
				newArgs = new String[ args.length - i];
				
				for ( int k = i; k < args.length; k ++)
				{
					newArgs[ k - i] = args [k];
				}
				
				break;
			}
		}		
		
		envArgs.putAll( commandArgs );
		
		String v = commandArgs.getProperty("i");
		if (v != null)
		{
			envArgs.put("key-file", v);
		}
		
		if (envArgs.size() == 0 
			|| envArgs.getProperty("server") == null 
			|| envArgs.getProperty("user") == null 
			|| command.length() == 0)
		{
			usage();
		}

		String tv = envArgs.getProperty("timeout");
		if (tv == null)
		{
			TimeoutValue = 5 * 60 * 10; /* 5 minutes */
		}
		else
		{
			TimeoutValue = Integer.parseInt(tv) * 10;
		}
		
		RemoteHomePath = envArgs.getProperty("home","").replace('\\','/').trim();
		
		if (RemoteHomePath.length() > 0 && !RemoteHomePath.endsWith("/"))
		{
			RemoteHomePath += "/";
		}
		
		RemoteCurrentPath = commandArgs.getProperty("p","").replace('\\','/').trim();
		if (RemoteCurrentPath.length() > 0 && !RemoteCurrentPath.endsWith("/"))
		{
			RemoteCurrentPath += "/";
		}
		
		while (RemoteCurrentPath.startsWith("/"))
		{
			RemoteCurrentPath = RemoteCurrentPath.substring(1);
		}
		
		RemoteFullCurrentPath = RemoteHomePath + RemoteCurrentPath;
		
		/* Create a connection instance */

		conn = new Connection ( envArgs.getProperty("server") );

		/* Now connect */

		conn.connect();
		
		/* Authenticate.
		 * If you get an IOException saying something like
		 * "Authentication method password not supported by the server at this stage."
		 * then please check the FAQ.
		 */
		
		boolean isAuthenticated;
		
		if ( envArgs.getProperty("key-file") == null)
		{
			isAuthenticated = conn.authenticateWithPassword(
						envArgs.getProperty("user"),
						envArgs.getProperty("password",""));
		}
		else
		{
			isAuthenticated = conn.authenticateWithPublicKey(
					envArgs.getProperty("user"),
					new File(envArgs.getProperty("key-file")), "");
		}

		if (isAuthenticated == false)
		{
			conn.close();
			throw new IOException("Authentication failed.");
		}

		/* Create a session */

		sess = conn.openSession();
		if (sess == null)
		{
			conn.close();
			return;
		}
		
		if (command.equals("shell"))
		{
			cmd_shell (newArgs);
		}
		else if (command.equals("run"))
		{
			cmd_run (newArgs);
		}
		else if (command.equals("get"))
		{
			cmd_get (newArgs);
		}
		else if (command.equals("put"))
		{
			cmd_put (newArgs);
		}
		else if (command.equals("getenv"))
		{
			cmd_getenv (newArgs);
		}
		else if (command.equals("setenv"))
		{
			cmd_setenv (newArgs);
		}
		else if (command.equals("env"))
		{
			cmd_env (newArgs);
		}
		else if (command.equals("setup"))
		{
			cmd_setup (newArgs);
		}
		else
		{
			usage();
		}

		sess.close();
		conn.close();
	}
}
